using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.IO;
using System.Threading;
using SimpleMessageQueue;


namespace slcl
{
    /// <summary>
    /// Since we are using async callbacks we'll use Grid's message queue to dish out responses
    /// and events. I think. Well, i'm really undecided right now. ;-P
    /// My current thought is to use a Dictionary of requests to send back responses and 
    /// absence in the dictionary means it's the event queue read "loop"
    /// </summary>
    public class CAPSManager
    {
         public struct RequestHolder
        {
            public string Capability;
            public string URI;
            public HttpWebRequest Request;
            public IAsyncResult Result;
            public byte[] Data;
            public ManualResetEvent Completed;
            public bool Success;
            public string Message;

            public RequestHolder(string Cap, string uri, byte[] data)
            {
                Capability = Cap;
                URI = uri;
                Data = data;
                Completed = new ManualResetEvent(false);
                Message = string.Empty;
                Result = null;
                Success = false;
                Request = null;
            }
        }

        public enum Command
        {
            Send,       // send fiel up to caps.
            Request,    // make a request. Not certain if this is different enough from Send to keep around.
            Start,      //start the loop. Send the seed with this.
            Stop,       // stop the loop.
            ReSeed,     // drop the current caps link and retrieve the new caps objects.
            Register,   // register to be Ack'd on events
            Unregister, // Unregister existing registrations
            Shutdown    // standard MQueue shutdown.
        }

        public enum Event
        {
            EventQueueStarted,
            EventQueueStopped,
            Event,
            Shutdown
        }

        public enum Capabilities
        {
            Seed,
            MapLayer,
            MapLayerGod,
            NewAgentInventory,
            NewFileAgentInventory,
            EventQueueGet,
            UpdateGestureAgentInventory,
            UpdateNotecardAgentInventory,
            UpdateScriptAgentInventory,
            UpdateGestureTaskInventory,
            UpdateNotecardTaskInventory,
            UpdateScriptTaskInventory,
            SendPostcard,
            ViewerStartAuction,
            ParcelGodReserveForNewbie,
            SendUserReport,
            SendUserReportWithScreenshot,
            RequestTextureDownload
        }

        public enum CAPSEvent
        {
            TeleportFinish         // so far we know of only this one.
        }

        public struct CAPSEventCarrier
        {
            public string Event;
            public Hashtable Body;
        }

        ArrayList req ;

        /// <summary>
        /// our messaging queue.
        /// </summary> 
        public MessageQueue MQueue;

        /// <summary>
        /// the grid that this caps "set" is for.
        /// </summary>
        Grid Grid;

        /// <summary>
        /// for our event queue loop. Let's us check in on the request's status.
        /// </summary>
        IAsyncResult SeedResult;
        
        /// <summary>
        /// State of the event queue, is it actively reading right now or is it stopped?
        /// </summary>
        public bool ReadingEventQueue = false;

        /// <summary>
        /// Should be we doing the event queue?
        /// </summary>
        public bool EventQueueActive = false;

        /// <summary>
        /// Our output data for event queue read.
        /// </summary>
        byte[] SeedRequestData = null;

        /// <summary>
        /// where we aim for each CAPS request.
        /// </summary>
        Dictionary<string, string> Targets;

        /// <summary>
        /// what messages we should ack on events.
        /// </summary>
        Dictionary<string, List<CommandMessage>> Registrations;

        /// <summary>
        /// our outstanding requests OTHER than EventQueueRead.
        /// </summary>
        List<RequestHolder> Requests;

        public CAPSManager(Grid g)
        {
            Grid = g;
            Targets = new Dictionary<string, string>();
            Requests = new List<RequestHolder>();
            Registrations = new Dictionary<string, List<CommandMessage>>();

            if (Grid.Client.Settings.USE_INDEPENDANT_QUEUES)
            {
                MQueue = new MessageQueue(this, true);
            }
            else
            {
                MQueue = Grid.Client.MQueue;
            }
            MQueue.RegisterPacketCallback(Message.MessageType.Command
                                          , new MessageQueue.MessageCallback(CommandMessageHandler));
            //all of the things that we are interested in on this queue.
            // and serialize it just once.
            // cpu cycles man, they are precious! Even on the biggest honker ever...

            req = new ArrayList();

            // My original plan was to use an enum of capabilities,
            // but converting enum to string value is getting troublesome.

            // convert our enum into strings for the seed request.
       //     foreach (string capability in Enum.GetNames(typeof(Capabilities)))
        //    {
        //        req.Add(capability);
       //         Log("Requesting capability " + capability, LogLevel.Info);
       //     }
            req.Add("SeedCapabilities"); // special case for our inital seed.
            req.Add("MapLayer");
            req.Add("MapLayerGod");
            req.Add("NewAgentInventory");
            req.Add("NewFileAgentInventory");
            req.Add("EventQueueGet");
            req.Add("UpdateGestureAgentInventory");
            req.Add("UpdateNotecardAgentInventory");
            req.Add("UpdateScriptAgentInventory");
            req.Add("UpdateGestureTaskInventory");
            req.Add("UpdateNotecardTaskInventory");
            req.Add("UpdateScriptTaskInventory");
            req.Add("SendPostcard");
            req.Add("ViewerStartAuction");
            req.Add("ParcelGodReserveForNewbie");
            req.Add("SendUserReport");
            req.Add("SendUserReportWithScreenshot");
            req.Add("RequestTextureDownload");

            // and now to one long array of bytes via serialize...
            SeedRequestData = LLSD.LLSDSerialize(req);
        }

        /// <summary>
        /// Simplified starting point for Grid etc to start the loop.
        /// </summary>
        /// <param name="CapsSeedURI">Where to query for our inital caps targets</param>
        /// <returns></returns>
        public bool GetTargetsFromSeed()
        {
            if (!Targets.ContainsKey("SeedCapabilities"))
            {
                Log("GetTargetsFromSeed called without target SeedCapabilities available.", LogLevel.Warning);
                return false;
            }
            Log("Starting CAPS Seed request", LogLevel.Info);
            EventQueueActive = true;
            RequestHolder SeedRequest = MakeRequest("SeedCapabilities", Targets["SeedCapabilities"], SeedRequestData);
            SeedRequest.Completed.WaitOne(5000, false);
            Log("Seed request completed.", LogLevel.Debug);
            return SeedRequest.Success;
        }

        public bool GetEventQueue()
        {
            if (!EventQueueActive)
            {
                Log("EventQueueGet when EventQueue is NOT enabled.", LogLevel.Warning);
                return false;
            }
            if (!Targets.ContainsKey("EventQueueGet"))
            {
                Log("Missing EventQueueGet in Targets.", LogLevel.Error);
                return false;
            }
            Log("Making EventQueueGet request", LogLevel.Debug);
            MakeRequest("EventQueueGet", Targets["EventQueueGet"], new byte[] { });
            Log("Request created. returning.", LogLevel.Debug);

            return true;
        }

        void Log(string Msg, LogLevel lvl)
        {
         Grid.Log("CAPS: " + Msg, lvl);
        }
        /// <summary>
        /// makes a request out to the website and returns a result handle.
        /// </summary>
        /// <param name="CapsURI"></param>
        /// <param name="data"></param>
        /// <param name="callback"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public RequestHolder MakeRequest(string Capability, string URI, byte[] data) //, AsyncCallback callback)
        {
            // new request.
            RequestHolder Holder = new RequestHolder(Capability, URI, data);
            try
            {
                Log("CAPS: starting async request for " + URI + " with data \r\n-----------\r\n" + stringify(data) + "\r\n----------\r\n", LogLevel.Debug);

                HttpWebRequest Request = (HttpWebRequest)HttpWebRequest.Create(URI);
                Holder.Request = Request;
                Request.KeepAlive = false;
                Request.Timeout = Grid.Client.Settings.CAPS_TIMEOUT;
                Request.Method = "POST";
                Request.ContentLength = data.Length;

                // spit it out to the CAPS server.
                // Is it valid to do it this way? Sync with the write and async with the read? dunno...
                Requests.Add(Holder);
                Request.BeginGetRequestStream(new AsyncCallback(WriteRequestStream), Holder);
                return Holder;
            }
            catch (WebException e)
            {
                Log(e.ToString(), LogLevel.Error);
                Holder.Success = false;
                Holder.Completed.Set();
                Holder.Message = e.Message;
                return Holder;
            }
        }

        void WriteRequestStream(IAsyncResult beginResult)
        {

            RequestHolder Holder = (RequestHolder)beginResult.AsyncState;
            Log("Write completing for " + Holder.Capability, LogLevel.Debug);
            Stream ReqStream = Holder.Request.EndGetRequestStream(beginResult);
            ReqStream.Write(Holder.Data, 0, Holder.Data.Length);
            ReqStream.Close();

            IAsyncResult Result = Holder.Request.BeginGetResponse(new AsyncCallback(RequestCompleted), Holder);
            Holder.Completed.Reset();
            Holder.Result = Result;
            // store for later retrieval in RequestCompleted();

        }

        /// <summary>
        /// reads the completed sync http request call.
        /// </summary>
        /// <param name="result">Handle for the request that completed</param>
        public void RequestCompleted(IAsyncResult result)
        {

            // two layer try/catch here. the first is for overall operations.
            // The second is to catch 404 and other errors.
            try
            {
                Log("CAPS: Reading result of async request.", LogLevel.Debug);

                // the callback holds our request object. Use it to retrieve out RequestHolder object.
                RequestHolder Holder = (RequestHolder)result.AsyncState;
                // so that we don't accidentally kill this request on shutdown since it's completed.
                Requests.Remove(Holder);
                byte[] buffer;
                BinaryReader Reader;
                WebResponse Response;

                // this is the only area that should fail on us for various reasons
                //as a part of the normal operations.
                try
                {
                    Response = Holder.Request.EndGetResponse(result);
                    Reader = new BinaryReader(Response.GetResponseStream());
                    buffer = Reader.ReadBytes((int)Response.ContentLength);
                    Response.Close();
                }
                catch (Exception e)
                {
                    Holder.Success = false;
                    if (e.Message.Contains("404"))
                    {
                        // This capability no longer exists, disable it
                        // need to track what KIND of capability this was for.
                        Log("Caps not supported anymore: " + Holder.Capability + " -> " + Holder.Request.Address.ToString(), LogLevel.Warning);
                        Targets.Remove(Holder.Capability);
                    }                    
                    else
                    {
                        if (!e.Message.Contains("canceled"))
                        {
                            Log("Retrying " + Holder.Capability + " --> " + e.Message, LogLevel.Error);
                            MakeRequest(Holder.Capability, Holder.URI, Holder.Data);
                        }
                        else
                        {
                            Log("Request " + Holder.Capability + " was aborted.", LogLevel.Info);
                        }
                    }
                    Holder.Completed.Set();
                    return;
                }


                Log("CAPS READ:\r\n" + buffer, LogLevel.Debug);

                Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer);
                // now we do somethign with ths result.
                // if it's the seed. special handling.
                if (Holder.Capability == "SeedCapabilities")
                {
                    ProcessSeedResult(resp, Holder);
                }
                else if (Holder.Capability == "EventQueueGet")
                {
                    ProcessEvents(resp, Holder);
                }
                else  // Otherwise it's a different type of response. 
                {
                    ProcessResponse(resp, Holder);
                }
                Holder.Success = true;
                // signal if anyone is watching.
                Holder.Completed.Set();
            }
            catch (Exception except)
            {
                Log(except.ToString(), LogLevel.Critical);
            }
        }

        void ProcessResponse(Hashtable resp, RequestHolder Holder)
        {
            Log("Other response.", LogLevel.Debug);
            // FIXME: for now we send over the buffer.
            CAPSEventCarrier carrier;
            carrier.Event = Holder.Capability;
            carrier.Body = resp;
            MQueue.MatchRegistered(Holder.Capability, AckMessage.StatusType.Completed, carrier);
        }

        void ProcessEvents(Hashtable resp, RequestHolder Holder)
        {
            Log("Event Queue response.", LogLevel.Debug);
            // event queue returned, we let others know what's up.
            MQueue.MatchRegistered(Event.Event, AckMessage.StatusType.Completed, resp);
            if (!GetEventQueue())
            {
                Log("Failed to start a new EventQueueGet call", LogLevel.Warning);
            }
        }

        void ProcessSeedResult(Hashtable resp, RequestHolder Holder)
        {
            Log("SeedCaps response processing.", LogLevel.Debug);
            foreach (string cap in resp.Keys)
            {
                Log(String.Format("Got cap {0}: {1}", cap, (string)resp[cap]), LogLevel.Info);
                if (Targets.ContainsKey(cap))
                    Targets.Remove(cap);
                Targets.Add(cap, (string)resp[cap]);
            }
        }

        public void CommandMessageHandler(Message msg)
        {
            if (!(msg is CommandMessage)) return;
            if (!(((CommandMessage)msg).Command is Command)) return;
            Log("CAPS: Handling command " + ((Command)((CommandMessage)msg).Command).ToString(), LogLevel.Info);
            switch ((Command)((CommandMessage)msg).Command)
            {
                case Command.Shutdown:
                    Log("CAPS Shutting down.", LogLevel.Info);
                    MQueue.Shutdown(); // so that we don't process anymore messages.
                    // toast all our outstanding requests.
                    foreach (RequestHolder request in Requests)
                    {
                        request.Request.Abort();
                    }
                    Log("CAPS Shutdown Complete.", LogLevel.Info);
                    break;
                case Command.Start:
                    Log("CAPS Starting", LogLevel.Info);
                    if (null == msg.Data)
                    {
                        MQueue.Ack((CommandMessage)msg, AckMessage.StatusType.Failed, "Missing seed capabilities!");
                    }
                    StopEventQueueGet();
                    Targets.Add("SeedCapabilities", (string)msg.Data);
                    GetTargetsFromSeed();
                    StartEventQueueGet();
                    Log("CAPS Startup complete.", LogLevel.Info);
                    break;
                case Command.Stop:
                    Log("Stopping CAPS.", LogLevel.Info);
                    StopEventQueueGet();
                    Targets.Clear();
                    Log("Done.", LogLevel.Info);
                    break;
            }
        }

        string stringify(byte[] data)
        {
            string ret = "";
            foreach(byte b in data)
            {
             ret +=  System.Convert.ToChar(b);
            }
            return ret;
        }

        void StartEventQueueGet()
        {
            Log("Starting event queue get", LogLevel.Info);
            if (!Targets.ContainsKey("EventQueueGet"))
            {
                Log("StartEventQueue requested without seed capabilities!", LogLevel.Warning);
                return;
            }
            EventQueueActive = true;
            if (!GetEventQueue())
            {
                return;
            }
            MQueue.MatchRegistered(Event.EventQueueStarted, AckMessage.StatusType.Completed);
            Log("Event queue get complete", LogLevel.Info);
        }

        void StopEventQueueGet()
        {
            if (!EventQueueActive)
            {
                Log("StopEventQueueGet called when event queue get is NOT active!", LogLevel.Warning);
                return;
            }
            EventQueueActive = false;
            Targets.Clear();
            foreach (RequestHolder holder in Requests)
            {
                holder.Request.Abort();
            }
            MQueue.MatchRegistered(Event.EventQueueStopped, AckMessage.StatusType.Completed);
        }
    }
}
